<DynamicHeading className="sr-only" level={headingLevel}>{label}</DynamicHeading>
<FocusRestoration realm={focusRealm}>
  <div class="timeline" role="feed">
    {#if components}
      <svelte:component this={components.listComponent}
                  component={components.listItemComponent}
                  realm="{$currentInstance + '/' + timeline}"
                  {makeProps}
                  items={itemIds}
                  showFooter={true}
                  footerComponent={LoadingFooter}
                  showHeader={$showHeader}
                  headerComponent={MoreHeaderVirtualWrapper}
                  {headerProps}
                  {scrollToItem}
                  on:scrollToBottom="onScrollToBottom()"
                  on:scrollToTop="onScrollToTop()"
                  on:scrollTopChanged="onScrollTopChanged(event)"
                  on:initialized="initialize()"
                  on:noNeedToScroll="onNoNeedToScroll()"
      />
    {/if}
  </div>
</FocusRestoration>
<Shortcut scope="global" key="." on:pressed="showMoreAndScrollToTop()" />
<ScrollListShortcuts />
<script>
  import { store } from '../../_store/store.js'
  import DynamicHeading from '../DynamicHeading.html'
  import Status from '../status/Status.html'
  import LoadingFooter from './LoadingFooter.html'
  import MoreHeaderVirtualWrapper from './MoreHeaderVirtualWrapper.html'
  import ScrollListShortcuts from '../shortcut/ScrollListShortcuts.html'
  import Shortcut from '../shortcut/Shortcut.html'
  import { importVirtualList } from '../../_utils/asyncModules/importVirtualList.js'
  import { importList } from '../../_utils/asyncModules/importList.js'
  import { importStatusVirtualListItem } from '../../_utils/asyncModules/importStatusVirtualListItem.js'
  import { importNotificationVirtualListItem } from '../../_utils/asyncModules/importNotificationVirtualListItem.js'
  import { timelines } from '../../_static/timelines.js'
  import {
    fetchMoreItemsAtBottomOfTimeline,
    setupTimeline,
    showMoreItemsForTimeline,
    showMoreItemsForThread,
    showMoreItemsForCurrentTimeline
  } from '../../_actions/timeline.js'
  import { scheduleIdleTask } from '../../_utils/scheduleIdleTask.js'
  import { mark, stop } from '../../_utils/marks.js'
  import { isEqual } from '../../_thirdparty/lodash/objects.js'
  import { doubleRAF } from '../../_utils/doubleRAF.js'
  import { observe } from 'svelte-extras'
  import { createMakeProps } from '../../_actions/createMakeProps.js'
  import { showMoreAndScrollToTop } from '../../_actions/showMoreAndScrollToTop.js'
  import FocusRestoration from '../FocusRestoration.html'
  import { formatIntl } from '../../_utils/formatIntl.js'

  export default {
    oncreate () {
      console.log('timeline oncreate()')
      setupTimeline()
      this.setupStreaming()
      this.setupAsyncComponents()
    },
    data: () => ({
      LoadingFooter,
      MoreHeaderVirtualWrapper,
      Status,
      scrollTop: 0,
      components: undefined
    }),
    computed: {
      // For threads, it's simpler to just render all items as a pseudo-virtual list
      // due to need to scroll to the right item and thus calculate all item heights up-front.
      // Here we lazy-load both the virtual list component itself as well as the component
      // it renders.
      componentsPromise: ({ timelineType }) => {
        return Promise.all([
          timelineType === 'status'
            ? importList()
            : importVirtualList(),
          timelineType === 'notifications'
            ? importNotificationVirtualListItem()
            : importStatusVirtualListItem()
        ]).then(results => ({
          listComponent: results[0],
          listItemComponent: results[1]
        }))
      },
      makeProps: ({ $currentInstance, timelineType, timelineValue }) => (
        createMakeProps($currentInstance, timelineType, timelineValue)
      ),
      label: ({ timeline, $currentInstance, timelineType, timelineValue }) => {
        if (timelines[timeline]) {
          return formatIntl('intl.statusesTimelineOnInstance', {
            timeline: timelines[timeline].label,
            instance: $currentInstance
          })
        }

        switch (timelineType) {
          case 'tag':
            return formatIntl('intl.statusesHashtag', { hashtag: timelineValue })
          case 'status':
            return 'intl.statusesThread'
          case 'account':
            return 'intl.statusesAccountTimeline'
          case 'list':
            return 'intl.statusesList'
          case 'notifications':
            return formatIntl('intl.notificationsOnInstance', { instance: $currentInstance })
        }
      },
      timelineType: ({ $currentTimelineType }) => $currentTimelineType,
      timelineValue: ({ $currentTimelineValue }) => $currentTimelineValue,
      // Scroll to the first item if this is a "status in own thread" timeline.
      // Don't scroll to the first item because it obscures the "back" button.
      scrollToItem: ({ timelineType, timelineValue, $firstTimelineItemId }) => (
        timelineType === 'status' &&
        $firstTimelineItemId &&
        timelineValue !== $firstTimelineItemId &&
        timelineValue
      ),
      itemIds: ({ $filteredTimelineItemSummaries }) => (
        $filteredTimelineItemSummaries && $filteredTimelineItemSummaries.map(_ => _.id)
      ),
      itemIdsToAdd: ({ $filteredTimelineItemSummariesToAdd }) => (
        $filteredTimelineItemSummariesToAdd && $filteredTimelineItemSummariesToAdd.map(_ => _.id)
      ),
      headerProps: ({ itemIdsToAdd }) => {
        return {
          count: itemIdsToAdd ? itemIdsToAdd.length : 0,
          onClick: showMoreItemsForCurrentTimeline
        }
      },
      focusRealm: ({ $currentInstance, timeline }) => `${$currentInstance}-${timeline}`,
      headingLevel: ({ timeline, timelineType }) => timeline === 'home' || timelineType === 'status' ? 2 : 1
    },
    store: () => store,
    methods: {
      observe,
      initialize () {
        const { initializeStarted } = this.get()
        if (initializeStarted) {
          return
        }
        this.set({ initializeStarted: true })
        mark('initializeTimeline')
        doubleRAF(() => {
          console.log('timeline initialized')
          this.store.set({ timelineInitialized: true })
          stop('initializeTimeline')
        })
      },
      onScrollTopChanged (scrollTop) {
        this.set({ scrollTop })
      },
      onScrollToBottom () {
        const { timelineType } = this.get()
        const { timelineInitialized, runningUpdate, disableInfiniteScroll } = this.store.get()
        if (!timelineInitialized ||
            runningUpdate ||
            disableInfiniteScroll ||
            timelineType === 'status') { // for status contexts, we've already fetched the whole thread
          return
        }
        const { currentInstance } = this.store.get()
        const { timeline } = this.get()
        /* no await */ fetchMoreItemsAtBottomOfTimeline(currentInstance, timeline)
      },
      onScrollToTop () {
        const { shouldShowHeader } = this.store.get()
        if (shouldShowHeader) {
          this.store.setForCurrentTimeline({
            showHeader: true,
            shouldShowHeader: false
          })
        }
      },
      setupStreaming () {
        const { currentInstance, disableInfiniteScroll } = this.store.get()
        const { timeline, timelineType } = this.get()
        const handleItemIdsToAdd = () => {
          const { itemIdsToAdd } = this.get()
          if (!itemIdsToAdd || !itemIdsToAdd.length) {
            return
          }
          mark('handleItemIdsToAdd')
          const { scrollTop } = this.get()
          const {
            shouldShowHeader,
            showHeader
          } = this.store.get()
          if (timelineType === 'status') {
            // this is a thread, just insert the statuses already
            showMoreItemsForThread(currentInstance, timeline)
          } else if (!disableInfiniteScroll && scrollTop === 0 && !shouldShowHeader && !showHeader) {
            // if the user is scrolled to the top and we're not showing the header, then
            // just insert the statuses. this is "chat room mode"
            showMoreItemsForTimeline(currentInstance, timeline)
          } else {
            // user hasn't scrolled to the top, show a header instead
            this.store.setForTimeline(currentInstance, timeline, { shouldShowHeader: true })
            // unless the user has disabled infinite scroll entirely
            if (disableInfiniteScroll) {
              this.store.setForTimeline(currentInstance, timeline, { showHeader: true })
            }
          }
          stop('handleItemIdsToAdd')
        }
        this.observe('itemIdsToAdd', (newItemIdsToAdd, oldItemIdsToAdd) => {
          if (!newItemIdsToAdd ||
              !newItemIdsToAdd.length ||
              isEqual(newItemIdsToAdd, oldItemIdsToAdd)) {
            return
          }
          scheduleIdleTask(handleItemIdsToAdd)
        })
      },
      setupAsyncComponents () {
        this.observe('componentsPromise', async componentsPromise => {
          if (componentsPromise) {
            console.log('loading timeline components')
            const components = await componentsPromise
            console.log('loaded timeline components')
            this.set({ components })
          }
        })
      },
      onNoNeedToScroll () {
        // If the timeline doesn't need to scroll, then we can safely "preinitialize,"
        // i.e. render anything above the fold of the timeline. This avoids the affect
        // where the scrollable content appears to jump around if we need to scroll it.
        console.log('timeline preinitialized')
        this.store.set({ timelinePreinitialized: true })
      },
      showMoreAndScrollToTop
    },
    components: {
      ScrollListShortcuts,
      Shortcut,
      FocusRestoration,
      DynamicHeading
    }
  }
</script>
